module mbox.Dns;

import 
	dares.Types,
	mbox.Types;



struct MyDns {
	static	Mutex			mu ;
	static	Semaphore			sem ;
	static	Thread			_th ;
	static	MyDns*			_head ;
	static	Atomic!(MyDns*)		_now ;
	static	ares_channel		_channel ;
	
	static void Init() {
		mu			= new Mutex;
		sem			= new Semaphore ;
			
		auto ret	= ares_library_init(ARES_LIB_INIT_ALL);
		if ( ret  != ARES_SUCCESS) {
			printf( "Dns Error\n");
			Cin.get;
			dares.Types.exit(0);
		}
		
		ret	= ares_init(&_channel) ;
		if (ret != ARES_SUCCESS) {
			printf( "ares_init: %d %s\n", ret, ares_strerror(ret) );
			Cin.get;
			dares.Types.exit(0);
		}
		
		_th		= new Thread(&Run);
	}
		
	static void Start(){
		_th.start	;
	}
	
	static void Run() {
		timeval  tv;
		timeval *tvp;
		auto read_fds	= new SocketSet();
		auto write_fds	= new SocketSet();
		const MyDns* null_ptr	= null ;
		while( MyBox.isRunning.load() ) {
			MyDns* ptr	= _now.load() ;
			if( ptr is null ){
				sem.wait(1) ;
				continue ;
			}
			_now.store(null_ptr);
			ares_query(_channel,  ptr.name.ptr, ns_c_in , ns_t_mx, &my_cb, ptr );
			
			read_fds.reset ;
			write_fds.reset ;
			auto nfds	= ares_fds(_channel , read_fds.toFd_set, write_fds.toFd_set);
			if (nfds == 0) {
				Thread.sleep(.1);
				continue ;
			}
			tvp	= ares_timeout(_channel, NULL, &tv);
			auto count	= SocketSet.select(read_fds, write_fds , null, tvp);
			if (count < 0 ) {
				Thread.sleep(.1);
				continue ;
			}
			ares_process(_channel, read_fds.toFd_set, write_fds.toFd_set );
			Thread.sleep(.1) ;
		}
		
		ares_destroy(_channel);
		ares_library_cleanup();
	}
	
	static char[] Query(char[] dn ) {
		MyDns* ptr	= get(dn) ;
		if(  ptr is null ){
			return null ;
		}
		int	_ts		= Clock.now.unix.seconds ;
		if( ptr.dq !is null ) {
			foreach( r; ptr.dq.an ) {
				if( r.MX !is null ) {
					int delta	=  _ts - ptr.ts ;
					if( delta <= r.ttl ) {
						return r.MX.name ;
					}
				}
			}
			foreach( r; ptr.dq.ar ) {
				int delta	=  _ts - ptr.ts ;
				if( delta <= r.ttl ) {
					return r.Name ;
				}
			}
		}
		if( ptr._sem is null ){
			ptr._sem	=	new Semaphore ;
		}
		ptr.dq	= null ;
		ptr.ts		= Clock.now.unix.seconds ;
		while(  true  ) {
			MyDns* _ptr = _now.load() ;
			_ptr = _now.load() ;
			if( _ptr is null ) {
				_now.store(ptr);
				break;
			}
			Thread.sleep(.1) ;
		}
		
		ptr._sem.wait;
		
		if( ptr.dq is null ) {
			return null ;
		}
		
		foreach( r; ptr.dq.an ) {
			if( r.MX !is null ){
				return r.MX.name ;
			}
		}
		foreach( r; ptr.dq.ar ) {
			return r.Name ;
		}
		return null ;
	}
	
	static private MyDns* get(char[] _dn) {
		int		i	= rfind( Txt.trim(_dn), '@');
		if( i < 0 || i >= _dn.length ){
			return null ;
		}
		char[] dn		= new char[ _dn.length - i ] ;
		dares.Types.memcpy(&dn[0], &_dn[ i + 1],  dn.length -1 );
		dn[$-1]		= 0 ;
		MyDns*	_p, p ;
		synchronized( mu ) {
			if( _head is null ) {
				_head		= new MyDns;
				_head.name	= dn.dup ;
			}
			_p	= null ;
			p	= _head ;
			while( p !is null ) {
				if( p.name == dn ){
					_p	= p ;
					break ;
				}
				p	= p._next ;
			}
			if( _p is null ) {
				_p		= new MyDns;
				_p.name	= dn.dup ;
				_p._next	= _head ;
				_head	= _p ;
			}
		}
		return _p ;
	}
	
	
	static extern(C) void my_cb(void *arg,  int status, int timeouts, ubyte *abuf, int alen){
		MyDns* my	= cast(MyDns*) arg ;
		my.dq	= DNS_Query.Load(abuf, alen);
		my._sem.notify;
	}

	char[]		name ;
	DNS_Query*	dq ;
	uint			ts ;
	MyDns*		_next ;
	Semaphore		_sem ;
}